/**
Copyright (c) 2008 New Gonzo <john@newgonzo.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
**/


package com.newgonzo.cannonball.css.parser
{
	import com.newgonzo.cannonball.css.parser.selectors.DefaultSelectorFactory;
	import com.newgonzo.cannonball.css.parser.selectors.DefaultConditionFactory;
	import com.newgonzo.cannonball.w3c.css.sac.*;
	import com.newgonzo.commons.parsing.Token;
	import com.newgonzo.commons.parsing.Tokenizer;
	
	public class CSSParser implements IParser
	{
		private var handler:IDocumentHandler = new DefaultDocumentHandler();
		private var selFactory:ISelectorFactory = new DefaultSelectorFactory();
		private var condFactory:IConditionFactory = new DefaultConditionFactory();
		
		private var negated:Boolean = false;
		private var pseudoElement:String;
		
		private var tokenizer:Tokenizer;
		private var current:Token;
		
		public function CSSParser(selectorFactory:ISelectorFactory = null, conditionFactory:IConditionFactory = null)
		{
			this.selectorFactory = selectorFactory;
			this.conditionFactory = conditionFactory;
		}
		
		public function set documentHandler(value:IDocumentHandler):void
		{
			handler = value;
		}
		
		public function set selectorFactory(value:ISelectorFactory):void
		{
			if(!value) return;
			selFactory = value;
		}
		
		public function set conditionFactory(value:IConditionFactory):void
		{
			if(!value) return;
			condFactory = value;
		}
		
		public function parse(source:String):void
		{
			if(!tokenizer)
			{
				handler.startDocument(source);
				
				tokenizer = createTokenizer(source);
				nextIgnoreSpaces();
			}
			
			parseRule();
			
			handler.endDocument(source);
			
			// release the tokenizer
			tokenizer = null;
			current = null;
		}
		
		public function parseSelectors(source:String):ISelectorList
		{
			if(!tokenizer)
			{
				tokenizer = createTokenizer(source);
				nextIgnoreSpaces();
			}
			
			var list:ISelectorList = parseSelectorList();
			
			if(list.length > 0)
			{
				handler.startSelector(list);
			}
			
			return list;
		}
		
		protected function createTokenizer(source:String):Tokenizer
		{
			return new CSSTokenizer(source);
		}
		
		protected function skipSpaces():void
		{
			if(current.type == CSSToken.SPACE)
			{
				nextIgnoreSpaces();
			}
		}
		
		protected function nextIgnoreSpaces():Token
		{
			return next(true);
		}
		
		protected function next(ignoreSpaces:Boolean = false):Token
		{
			var token:Token;
			
			loop: while(true)
			{
				token = tokenizer.nextToken();
				
				switch(token.type)
				{
					case CSSToken.COMMENT:
						handler.comment(token.value);
						// keep looping
						break;
					
					// ignore spaces?
					case CSSToken.SPACE:
						if(!ignoreSpaces)
						{
							break loop;
						}
						
						// else keep looping
						break;
						
					default:
						break loop;
				}
			}
			
			current = token;
			return token;
		}
		
		protected function parseRule():void
		{
			switch(current.type)
			{
				case CSSToken.AT_RULE:
				
					switch(current.value)
					{
						case "import":
							nextIgnoreSpaces();
							parseImportRule();
							break;
							
						default:
							nextIgnoreSpaces();
							break;
					}
					
				default:
					parseRuleSet();
					break;
			}
		}
		
		protected function parseImportRule():void
		{
			var uri:String;
			
			switch(current.type)
			{
				case CSSToken.URI:
				case CSSToken.STRING:
					uri = current.value;
					break;
				
				default:
					throw new Error("Expecting URI or STRING for import rule but got " + current);
			}
			
			nextIgnoreSpaces();
			
			// mediaList = parseMediaList()...
			
			handler.importStyle(uri);
			
			if(current.type == CSSToken.SEMI_COLON)
			{
				nextIgnoreSpaces();
			}
		}
		
		protected function parseRuleSet():void
		{
			loop: while(current.type != Token.EOF)
			{
				//trace("Style loop: " + current);
				
				var selectors:ISelectorList = parseSelectorList();
			
				if(selectors.length > 0)
				{
					handler.startSelector(selectors);
				}
				
				// what's left after the selector was parsed
				switch(current.type)
				{
					case CSSToken.LEFT_CURLY_BRACE:
						
						// consume { and parse up to }
						nextIgnoreSpaces();
						parseStyleDeclaration();
						
						if(current.type != CSSToken.RIGHT_CURLY_BRACE)
						{
							throw new Error("Expected RIGHT_CURLY_BRACE but got " + current);
						}
						
						// consume right curly bracket and any spaces before
						nextIgnoreSpaces();
						
						break;
						
					case Token.EOF:
						// loop will break itself after this
						break;	
					
					default:
						throw new Error("Unexpected (unsupported?) " + current);
						break;
				}
				
				if(selectors.length > 0)
				{
					handler.endSelector(selectors);
				}
			}
		}
		
		protected function parseSelectorList():ISelectorList
		{
			var list:SelectorList = new SelectorList();
			list.append(parseSelector());
			
			while(true)
			{
				if(current.type != CSSToken.COMMA)
				{
					return list;
				}
				
				nextIgnoreSpaces();
				list.append(parseSelector());
			}
			
			return list;
			
		}
		
		protected function parseSelector():ISelector
		{
			var selector:ISelector = parseSimpleSelector();
			
			loop: while(true)
			{
				//trace("Selector start " + current);
				switch(current.type)
				{
					case CSSToken.IDENTIFIER:
					case CSSToken.ANY:
					case CSSToken.HASH:
					case CSSToken.DOT:
					case CSSToken.LEFT_BRACKET:
					case CSSToken.IDENTIFIER:
						//if(pseudoElement) throw new Error();
						selector = selFactory.createDescendantSelector(selector, parseSimpleSelector());
						break;
					
					// E + F	
					case CSSToken.PLUS:
						nextIgnoreSpaces();
						selector = selFactory.createDirectAdjacentSelector(1, selector, parseSimpleSelector());
						break;
						
					// E < F
					case CSSToken.PRECEDE:
						nextIgnoreSpaces();
						selector = selFactory.createChildSelector(selector, parseSimpleSelector());
						break;
						
					// E ~ F
					case CSSToken.TILDE:
						nextIgnoreSpaces();
						selector = selFactory.createGeneralSiblingSelector(1, selector, parseSimpleSelector());
						break;
					
					default:
						break loop;
				}
			}
			
			//trace("RETURNING SELECTOR");
			return selector;
		}
		
		protected function parseSimpleSelector():ISimpleSelector
		{
			var selector:ISimpleSelector;
			var condition:ICondition;
			var func:Token;
			var args:Array;
			
			//trace("Simple selector start: " + current);
			switch(current.type)
			{
				// E
				case CSSToken.IDENTIFIER:
					selector = selFactory.createElementSelector(null, current.value);
					next();
					break;
				
				// *	
				case CSSToken.ANY:
					selector = selFactory.createAnyNodeSelector();
					next();
					break;
					
				case Token.EOF:
					return selector;
					
				default:
					selector = selFactory.createAnyNodeSelector();
					break;
			}
			
			loop: while(true)
			{
					//trace("Condition switch " + current);
					switch(current.type)
					{	
						// #id
						case CSSToken.HASH:
							
							condition = condFactory.createIdCondition(current.value);
							next();
							break;
						
						// .class
						case CSSToken.DOT:
							
							// combine class conditions
							while(current.type == CSSToken.DOT)
							{
								// no spaces allowed after .
								next();
								
								if(current.type != CSSToken.IDENTIFIER)
								{
									throw new Error("Expected token IDENTIFIER after token DOT at " + current);
								}
								
								if(condition)
								{
									condition = condFactory.createAndCondition(condition, condFactory.createClassCondition(null, current.value));
								}
								else
								{
									condition = condFactory.createClassCondition(null, current.value);
								}
							
								nextIgnoreSpaces();
							}
							
							break;
						
						// [attr], [attr=val], [att~=value], etc
						case CSSToken.LEFT_BRACKET:
							
							// add to condition as a combinator if condition
							// already exists
							while(current.type == CSSToken.LEFT_BRACKET)
							{
								nextIgnoreSpaces();
								
								if(condition)
								{
									condition = condFactory.createAndCondition(condition, parseAttributeCondition());
								}
								else
								{
									condition = parseAttributeCondition();
								}
							}
							
							// break LEFT_BRACKET case
							break;
							
						// 
						case CSSToken.COLON:
													
							nextIgnoreSpaces();
							
							// ::
							if(current.type == CSSToken.COLON)
							{
								// pseudo-element
								nextIgnoreSpaces();
								
								if(current.type != CSSToken.IDENTIFIER)
								{
									throw new Error("Expected IDENTIFIER for pseudo-element but got " + current);
								}
								
								pseudoElement = current.value;
								nextIgnoreSpaces();
								break;
							}
							
							// pseudo-class
							//trace("Pseudo-class switch " + current);
							switch(current.type)
							{
								// :first-child
								case CSSToken.IDENTIFIER:
									
									switch(current.value)
									{
										case "only-of-type":
											condition = condFactory.createOnlyTypeCondition();
											break;
											
										case "only-child":
											condition = condFactory.createOnlyChildCondition();
											break;
											
										case "first-child":
											break;
											
										case "first-of-type":
											break;
									
										case "last-child":
											break;
										
										case "last-of-type":
											break;
											
										default:									
											condition = condFactory.createPseudoClassCondition(null, current.value);
											break;
									}
									
									nextIgnoreSpaces();
									break;
								
								case CSSToken.FUNCTION:
									
									func = current;
									nextIgnoreSpaces();
									args = parseArguments();
									
									//trace("Function switch: " + func + " + arguments: " + args.join(","));
									switch(func.value)
									{
										case "nth-child":
										case "nth-of-type":
											
											break;
											
										case "nth-last-child":
										case "nth-last-of-type":
										
											break;
											
										case "lang":
										
											condition = condFactory.createLangCondition(args[0] as String);
											break;
										
										default:
											//trace("Unknown function " + current + ", arguments: " + args.join(","));
											break;
									}
									
									// consume right brace
									next();
									
									//condition = condFactory.createPositionalCondition(
									break;
									
																	
							}				
							
							break;
							
						case CSSToken.NOT:
						
							if(negated)
							{
								throw new Error("The negation pseudo-class (:not()) cannot be nested.");
							}
							
							negated = true;
							
							// gobble :not(
							nextIgnoreSpaces();
							
							trace("NOT: Selector is " + selector);
							
							parseSimpleSelector()
							
							// end of function
							if(current.type != CSSToken.RIGHT_BRACE)
							{
								throw new Error("Expected RIGHT_BRACE but got " + current);
							}
							
							// end negation block
							negated = false;
							
							// gobble right brace
							nextIgnoreSpaces();
							
							break;
							
						case Token.EOF:
							break loop;
						
						default:
							//trace("Breaking loop -- not a simple selector token");
							break loop;
					} // end outer switch(current.type)
					
					if(condition)
					{
						selector = selFactory.createConditionalSelector(selector, condition);
						
						if(negated)
						{
							selector = selFactory.createNegativeSelector(selector);
						}
					}
			} // end loop:
			
			skipSpaces();
			return selector;
		}
		
		protected function parseAttributeCondition():IAttributeCondition
		{
			// attribute test
			var attribute:Token;
			var operation:Token;
			var compare:Token;
			var condition:IAttributeCondition;
			
			// attr has to be an identifier
			if(current.type != CSSToken.IDENTIFIER)
			{
				throw new Error("Expeced IDENTIFIER after LEFT_BRACKET but got " + current);
			}
			
			attribute = current;
			operation = nextIgnoreSpaces();
			
			//trace("Attribute " + attribute);
			//trace("Operation switch " + operation);
			switch(operation.type)
			{
				// [attr]
				case CSSToken.RIGHT_BRACKET:
				
					condition = condFactory.createAttributeCondition(attribute.value, null, false, null);
					// consume bracket and leading spaces
					nextIgnoreSpaces();
					break;
				
				// [attr=, attr~=, attr^=, etc
				case CSSToken.EQUAL:
				case CSSToken.INCLUDES:
				case CSSToken.DASHMATCH:
				case CSSToken.PREFIXMATCH:
				case CSSToken.SUFFIXMATCH:
				case CSSToken.SUBSTRINGMATCH:
				
					nextIgnoreSpaces();
					
					// "value" or value
					//trace("Operation value switch " + current);
					switch(current.type)
					{
						case CSSToken.STRING:
						case CSSToken.IDENTIFIER:
							
							compare = current;
							nextIgnoreSpaces();
							break;
							
						default:
							throw new Error("Expected STRING or IDENTIFIER but got " + current);
					}
					
					// ] should close the condition if all went well
					if(current.type != CSSToken.RIGHT_BRACKET)
					{
						throw new Error("Expected RIGHT_BRACKET after condition but got " + current);
					}
					
					// create condition based on type of operation
					switch(operation.type)
					{
						case CSSToken.EQUAL:
							
							condition = condFactory.createAttributeCondition(attribute.value, null, false, compare.value);
							break;
							
						case CSSToken.INCLUDES:
							condition = condFactory.createOneOfAttributeCondition(attribute.value, null, false, compare.value);
							break;
							
						case CSSToken.DASHMATCH:
							condition = condFactory.createBeginHyphenAttributeCondition(attribute.value, null, false, compare.value);
							break;
							
						case CSSToken.PREFIXMATCH:
							condition = condFactory.createPrefixAttributeCondition(attribute.value, null, false, compare.value);
							break;
							
						case CSSToken.SUFFIXMATCH:
							condition = condFactory.createSuffixAttributeCondition(attribute.value, null, false, compare.value);
							break;
							
						case CSSToken.SUBSTRINGMATCH:
							condition = condFactory.createSubstringAttributeCondition(attribute.value, null, false, compare.value);
							break;
					}
					
					// eat trailing bracket and preceding whitespace
					nextIgnoreSpaces();
			}
		
			return condition;
		}
		
		/**
		* Divides arguments into strings for general use
		*/
		protected function parseArguments():Array
		{
			var args:Array = new Array();
			var arg:String = "";
			
			// whitespace before and after an argument is chopped
			loop: while(true)
			{
				//trace("Argument switch " + current);
				switch(current.type)
				{
					case CSSToken.COMMA:
						
						args.push(arg);
						nextIgnoreSpaces();
						arg = "";
						break;
				
					case CSSToken.RIGHT_BRACE:
						args.push(arg);
						break loop;
						
					case Token.EOF:
						throw new Error("Missing ending RIGHT_BRACE");
						
					default:						
						arg += current.value;
						next();
						break;
				}
			}
			
			return args;
		}
		
		/**
		* This method should handle style declarations in a way
		* that allows for inline CSS
		*/
		protected function parseStyleDeclaration(inline:Boolean = false):void
		{	
			var foundColon:Boolean = false;
			var prop:String;
			
			loop: while(true)
			{
				switch(current.type)
				{
					case CSSToken.IDENTIFIER:
						
						prop = current.value;
						nextIgnoreSpaces()
						
						if(current.type != CSSToken.COLON)
						{
							throw new Error("Expecting COLON but got " + current);
						}
						
						// consume colon
						nextIgnoreSpaces();
						
						// parse expression following : up to ;
						var exp:ILexicalUnit = parseExpression();
						
						if(exp)
						{
							var important:Boolean = false;
							
							if(current.type == CSSToken.IMPORTANT_SYMBOL)
							{
								important = true;
								nextIgnoreSpaces();
							}
						
							handler.property(prop, exp, important);
						}
						break;
						
					case CSSToken.COLON:
						// colon occurred before identifier
						throw new Error("Unexpected COLON at " + current);
					
					case Token.EOF:
					
						if(!inline)
						{
							throw new Error("Missing ending RIGHT_CURLY_BRACE");
						}
					
					case CSSToken.SEMI_COLON:

						nextIgnoreSpaces();
						break;
						
					case CSSToken.RIGHT_CURLY_BRACE:
					
						if(inline)
						{
							throw new Error("Unexpected RIGHT_CURLY_BRACE at " + current);
						}
						else
						{
							break loop;
						}
						
					default:
						throw new Error("Unexpected " + current + " while parsing style declaration");
				}
			}
		}
		
		
		
		protected function parseExpression(inFunction:Boolean = false):ILexicalUnit
		{
			//trace("parseExpression(" + arguments + ")");
			
			//trace("parseTerm start");
			var result:CSSLexicalUnitList = new CSSLexicalUnitList();
			result.append(parseTerm());
			
			loop: while(true)
			{
				var operator:Boolean = false;
				
				// operator block
				switch(current.type)
				{
					case CSSToken.COMMA:
						//trace("i see a comma.");
						operator = true;
						result.append(new CSSLexicalUnit(LexicalUnitTypes.SAC_OPERATOR_COMMA, current.value));
						nextIgnoreSpaces();
						break;
						
					default:
						break;
				}
				
				if(inFunction)
				{
					if(current.type == CSSToken.RIGHT_BRACE)
					{
						if(operator)
						{
							throw new Error("Unexpected RIGHT_BRACE after operator at " + current);
						}
						
						// end of function parameters
						break loop;
					}
					
					//trace("parseTerm function");
					result.append(parseTerm());
				}
				else
				{
					switch(current.type)
					{
						// Expression is returned at any of these ending tokens
						// If ending token is found immediately after an operator,
						// an error is thrown
						case CSSToken.IMPORTANT_SYMBOL:
						case CSSToken.SEMI_COLON:
						case CSSToken.RIGHT_CURLY_BRACE:
						case Token.EOF:
							if(operator)
							{
								throw new Error("Unexpected " + current);
							}
							
							break loop;
							
						default:
							//trace("parseTerm default");
							result.append(parseTerm());
					}
				}
			}
			
			return result;
		}
						
		protected function parseTerm():ILexicalUnit
		{
			//trace("parseTerm(): " + current);
		
			var result:ILexicalUnit
			var isNegative:Boolean = false;
			var numVal:String;
			var sign:Boolean = false;
			
			switch(current.type)
			{
				case CSSToken.MINUS:
					isNegative = true;
					sign = true;
					next();
					break;
					
				case CSSToken.PLUS:
					sign = true;
					next();
					break;
				
				default:
					isNegative = false;
					sign = false;
					break;
			}
			
			switch(current.type)
			{
				case CSSToken.NUMBER:
					
					result = parseNumber(isNegative);
					break;
					
				case CSSToken.FUNCTION:
				
					result = parseFunction();
					break;
					
				default:
					break;
			}
			
			// if we parsed a result, return it
			if(result)
			{
				return result;
			}
			
			// if we didn't complete a numeric unit but previously found a signed value, throw an error
			if(sign)
			{
				throw new Error("Error parsing signed unit");
			}
			
			
			switch(current.type)
			{
				case CSSToken.URI:
					result = new CSSLexicalUnit(LexicalUnitTypes.SAC_URI, current.value);
					break;
				
				case CSSToken.STRING:
					
					result = new CSSLexicalUnit(LexicalUnitTypes.SAC_STRING_VALUE, current.value);
					break;
					
				case CSSToken.IDENTIFIER:
				
					if(current.value.toLowerCase() == "inherit")
					{
						result = new CSSLexicalUnit(LexicalUnitTypes.SAC_INHERIT, current.value);
					}
					else
					{
						result = new CSSLexicalUnit(LexicalUnitTypes.SAC_IDENT, current.value);
					}
					
					break;
					
				case CSSToken.HEXADECIMAL:
				
					result = new CSSLexicalUnit(LexicalUnitTypes.SAC_RGBCOLOR, current.value);
					break;
					
				case CSSToken.HASH:
				
					// HASH tokens have their # removed in the interpreted value pruperty, so use source
					result = new CSSLexicalUnit(LexicalUnitTypes.SAC_RGBCOLOR, current.source);
					break;
					
				default:
					throw new Error("Unexpected " + current);
			}
			
			nextIgnoreSpaces();
			return result;
			
		}
		
		protected function parseNumber(isNegative:Boolean = false):ILexicalUnit
		{
			//trace("parseNumber(): " + arguments);
			
			var result:ILexicalUnit;
			
			var numVal:String = (isNegative ? "-" : "") + current.value;
			nextIgnoreSpaces();
			
			var dimension:ILexicalUnit = parseDimensionUnit();
			result = new CSSLexicalUnit(LexicalUnitTypes.SAC_REAL, numVal, dimension);
			
			return result;
		}
		
		protected function parseFunction():ILexicalUnit
		{
			//trace("parseFunction(): " + arguments);
			
			var result:ILexicalUnit;
			
			// gobble up function name
			var funcName:String = current.value;
			nextIgnoreSpaces();
			
			// get arguments TEMP
			var params:ILexicalUnit = new CSSLexicalUnit(LexicalUnitTypes.SAC_STRING_VALUE, parseArguments().join(","));
			
			// check for parenthesis close
			if(current.type != CSSToken.RIGHT_BRACE)
			{
				throw new Error("Expecting RIGHT_BRACE but got " + current);
			}
			
			// consume right paren ")"
			nextIgnoreSpaces();
			
			// check for dimensional unit
			var dimension:ILexicalUnit = parseDimensionUnit();
			
			switch(funcName.toLowerCase())
			{
				case "url":
					result = new CSSFunctionLexicalUnit(LexicalUnitTypes.SAC_URI, funcName, params, dimension);
					break;
					
				case "rgb":
					result = new CSSFunctionLexicalUnit(LexicalUnitTypes.SAC_RGBCOLOR, funcName, params, dimension);
					break;
					
				case "attr":
					result = new CSSFunctionLexicalUnit(LexicalUnitTypes.SAC_ATTR, funcName, params, dimension);
					break;
					
				case "counter":
					result = new CSSFunctionLexicalUnit(LexicalUnitTypes.SAC_COUNTER_FUNCTION, funcName, params, dimension);
					break;
					
				case "counters":
					result = new CSSFunctionLexicalUnit(LexicalUnitTypes.SAC_COUNTERS_FUNCTION, funcName, params, dimension);
					break;
					
				case "rect":
					result = new CSSFunctionLexicalUnit(LexicalUnitTypes.SAC_RECT_FUNCTION, funcName, params, dimension);
					break;
					
				default:
					result = new CSSFunctionLexicalUnit(LexicalUnitTypes.SAC_FUNCTION, funcName, params, dimension);
					break;
			}
			
			return result;
		}
		
		protected function parseDimensionUnit():ILexicalUnit
		{
			var result:ILexicalUnit;
			var unitText:String = current.value;
			
			//trace("parseDimensionUnit(): " + unitText);
			
			switch(current.type)
			{
				case CSSToken.PERCENTAGE:
						
					result = new CSSDimensionUnit(LexicalUnitTypes.SAC_PERCENTAGE, unitText);
					break;
					
				case CSSToken.IDENTIFIER:
					
					switch(unitText.toLowerCase())
					{
						case "pt":
							result = new CSSDimensionUnit(LexicalUnitTypes.SAC_POINT, unitText);
							break;
						case "pc":
							result = new CSSDimensionUnit(LexicalUnitTypes.SAC_PICA, unitText);
							break;
						case "px":
							result = new CSSDimensionUnit(LexicalUnitTypes.SAC_PIXEL, unitText);
							break;
						case "cm":
							result = new CSSDimensionUnit(LexicalUnitTypes.SAC_CENTIMETER, unitText);
							break;
						case "mm":
							result = new CSSDimensionUnit(LexicalUnitTypes.SAC_MILLIMETER, unitText);
							break;
						case "in":
							result = new CSSDimensionUnit(LexicalUnitTypes.SAC_INCH, unitText);
							break;
						case "em":
							result = new CSSDimensionUnit(LexicalUnitTypes.SAC_EM, unitText);
							break;
						case "ex":
							result = new CSSDimensionUnit(LexicalUnitTypes.SAC_EX, unitText);
							break;
						case "deg":
							result = new CSSDimensionUnit(LexicalUnitTypes.SAC_DEGREE, unitText);
							break;
						case "rad":
							result = new CSSDimensionUnit(LexicalUnitTypes.SAC_RADIAN, unitText);
							break;
						case "grad":
							result = new CSSDimensionUnit(LexicalUnitTypes.SAC_GRADIAN, unitText);
							break;
						case "s":
							result = new CSSDimensionUnit(LexicalUnitTypes.SAC_SECOND, unitText);
							break;
						case "ms":
							result = new CSSDimensionUnit(LexicalUnitTypes.SAC_MILLISECOND, unitText);
							break;
						case "hz":
							result = new CSSDimensionUnit(LexicalUnitTypes.SAC_HERTZ, unitText);
							break;
						case "khz":
							result = new CSSDimensionUnit(LexicalUnitTypes.SAC_KILOHERTZ, unitText);
							break;
						default:
							result = new CSSDimensionUnit(LexicalUnitTypes.SAC_DIMENSION, unitText);
							break;
					}
					
					break;
			}
			
			if(result)
			{
				nextIgnoreSpaces();
				return result;
			}
			
			return null;
		}
	}
}